/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the GLQuake source code.

 The GLQuake source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The GLQuake source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the GLQuake source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cg_snapshot.cpp - parses a snapshot received from the server-game
//


#include "cg_local.h"


/*
 ==============================================================================

 SNAPSHOT PARSING

 ==============================================================================
*/


/*
 ==================
 CG_DeltaEntity
 ==================
*/
static void CG_DeltaEntity (msg_t *msg, snapshot_t *snapshot, entityState_t *oldState, int number, bool unchanged){

	entityState_t	newState;

	if (unchanged)
		glqMemory->Copy(&newState, oldState, sizeof(entityState_t));
	else
		CG_ParseDeltaEntityState(msg, oldState, &newState, number);

	// Check if the entity was delta removed
	if (newState.number == ENTITYNUM_NONE){
		if (cg_showDeltaEntities->integerValue)
			G_Printf("%5i: remove: %i\n", glqMsgSystem->GetReadState(msg), number);

		return;
	}

	// Add the entity
	if (snapshot->numEntities == MAX_SNAPSHOT_ENTITIES)
		G_Error("CG_DeltaEntity: MAX_SNAPSHOT_ENTITIES hit");

	glqMemory->Copy(&snapshot->entityStates[snapshot->numEntities], &newState, sizeof(entityState_t));
	snapshot->numEntities++;
}

/*
 ==================
 CG_ParseSnapshotEntities
 ==================
*/
static void CG_ParseSnapshotEntities (msg_t *msg, snapshot_t *oldSnapshot, snapshot_t *newSnapshot){

	entityState_t	*oldState = NULL;
	int				oldIndex = 0;
	int				oldNum, newNum;

	newSnapshot->numEntities = 0;

	// Delta from the entities present in oldSnapshot
	if (!oldSnapshot)
		oldNum = 9999;
	else {
		if (oldIndex >= oldSnapshot->numEntities)
			oldNum = 9999;
		else {
			oldState = &oldSnapshot->entityStates[oldIndex];
			oldNum = oldState->number;
		}
	}

	while (1){
		// Read the entity index number
		newNum = glqMsgSystem->ReadBits(msg, ES_NUMBER_BITS);

		if (newNum == ENTITYNUM_NONE)
			break;

		if (glqMsgSystem->GetReadState(msg) > glqMsgSystem->GetWriteState(msg))
			G_Error("CG_ParseSnapshotEntities: end of message");

		while (oldNum < newNum){
			// One or more entities from the old snapshot are unchanged
			if (cg_showDeltaEntities->integerValue)
				G_Printf("%5i: unchanged: %i\n", glqMsgSystem->GetReadState(msg), oldNum);

			CG_DeltaEntity(msg, newSnapshot, oldState, oldNum, true);

			oldIndex++;

			if (oldIndex >= oldSnapshot->numEntities)
				oldNum = 9999;
			else {
				oldState = &oldSnapshot->entityStates[oldIndex];
				oldNum = oldState->number;
			}
		}

		if (oldNum == newNum){
			// Delta from previous state
			if (cg_showDeltaEntities->integerValue)
				G_Printf("%5i: delta: %i\n", glqMsgSystem->GetReadState(msg), newNum);

			CG_DeltaEntity(msg, newSnapshot, oldState, newNum, false);

			oldIndex++;

			if (oldIndex >= oldSnapshot->numEntities)
				oldNum = 9999;
			else {
				oldState = &oldSnapshot->entityStates[oldIndex];
				oldNum = oldState->number;
			}

			continue;
		}

		if (oldNum > newNum){
			// Delta from baseline
			if (cg_showDeltaEntities->integerValue)
				G_Printf("%5i: baseline: %i\n", glqMsgSystem->GetReadState(msg), newNum);

			CG_DeltaEntity(msg, newSnapshot, &cgs.baselines[newNum], newNum, false);

			continue;
		}
	}

	// Any remaining entities in the old snapshot are copied over
	while (oldNum != 9999){
		if (cg_showDeltaEntities->integerValue)
			G_Printf("%5i: unchanged: %i\n", glqMsgSystem->GetReadState(msg), oldNum);

		CG_DeltaEntity(msg, newSnapshot, oldState, oldNum, true);

		oldIndex++;

		if (oldIndex >= oldSnapshot->numEntities)
			oldNum = 9999;
		else {
			oldState = &oldSnapshot->entityStates[oldIndex];
			oldNum = oldState->number;
		}
	}

	if (cg_showDeltaEntities->integerValue){
		G_Printf("--------------------\n");
		G_Printf("%i entities\n", newSnapshot->numEntities);
		G_Printf("\n");
	}
}

/*
 ==================
 CG_ParseSnapshot
 ==================
*/
bool CG_ParseSnapshot (msg_t *msg, int sequence, int *time, bool *deltaCompressed){

	snapshot_t	*oldSnapshot, newSnapshot;
	int			i;

	// Parse the snapshot header
	newSnapshot.valid = false;
	newSnapshot.time = glqMsgSystem->ReadLong(msg);
	newSnapshot.ping = glqMsgSystem->ReadShort(msg);
	newSnapshot.rateDelayed = glqMsgSystem->ReadBool(msg);
	newSnapshot.deltaMessage = glqMsgSystem->ReadLong(msg);
	newSnapshot.messageSequence = sequence;

	// If the snapshot is delta compressed from data that we no longer have
	// available, we must suck up the rest of the snapshot, but not use it,
	// then ask for an uncompressed snapshot
	if (newSnapshot.deltaMessage <= 0){
		// Uncompressed snapshot
		oldSnapshot = NULL;

		newSnapshot.valid = true;
	}
	else {
		oldSnapshot = &cgs.snapshots[newSnapshot.deltaMessage & PACKET_MASK];

		if (!oldSnapshot->valid){
			// Should never happen
			G_Printf(S_COLOR_YELLOW "Delta from invalid snapshot\n");
		}
		else if (oldSnapshot->messageSequence != newSnapshot.deltaMessage){
			// The snapshot that the server did the delta from is too old, so
			// we can't reconstruct it properly
			G_Printf(S_COLOR_YELLOW "Delta snapshot too old\n");
		}
		else {
			// Valid delta parse
			newSnapshot.valid = true;
		}
	}

	// Parse the player state
	if (!oldSnapshot)
		CG_ParseDeltaPlayerState(msg, NULL, &newSnapshot.playerState);
	else
		CG_ParseDeltaPlayerState(msg, &oldSnapshot->playerState, &newSnapshot.playerState);

	// Parse the entities
	CG_ParseSnapshotEntities(msg, oldSnapshot, &newSnapshot);

	// Align to the next byte boundary
	glqMsgSystem->ReadAlign(msg);

	// If not valid, dump the entire thing now that it has been properly read
	if (!newSnapshot.valid){
		CG_UpdateSnapshotGraph(-1, false);
		return false;
	}

	if (cg_showSnapshots->integerValue)
		G_Printf("snapshot: %i, delta: %i, ping: %i\n", newSnapshot.messageSequence, newSnapshot.deltaMessage, newSnapshot.ping);

	// Update the lagometer's snapshot graph
	CG_UpdateSnapshotGraph(newSnapshot.ping, newSnapshot.rateDelayed);

	// Clear the valid flags of any snapshots between the last received and
	// this one, so if there was a dropped packet it won't look like something
	// valid to delta from next time we wrap around in the buffer
	i = cgs.latestSnapshot + 1;

	if (sequence - i >= PACKET_BACKUP)
		i = sequence - (PACKET_BACKUP - 1);

	for ( ; i < sequence; i++)
		cgs.snapshots[i & PACKET_MASK].valid = false;

	cgs.latestSnapshot = sequence;

	// Save the snapshot in the backup array for later delta comparisons
	cgs.snapshots[cgs.latestSnapshot & PACKET_MASK] = newSnapshot;

	// Let the engine know the snapshot's time and if it was delta compressed
	*time = newSnapshot.time;
	*deltaCompressed = (newSnapshot.deltaMessage > 0);

	return true;
}


/*
 ==============================================================================

 SNAPSHOT PROCESSING

 ==============================================================================
*/


/*
 ==================
 
 ==================
*/
static void CG_ResetEntity (entity_t *entity){

	entity->state = NULL;
	entity->nextState = NULL;

	entity->lastValid = false;

	// Clear the automatic looping sound
	entity->loopSound = NULL;

	// Free all renderer and sound system handles
	if (entity->entityHandle){
		glqRenderer->RemoveRenderEntity(cgs.scene, entity->entityHandle);
		entity->entityHandle = 0;
	}
	if (entity->lightHandle){
		glqRenderer->RemoveRenderLight(cgs.scene, entity->lightHandle);
		entity->lightHandle = 0;
	}
	if (entity->emitterHandle){
		glqSound->RemoveSoundEmitter(entity->emitterHandle, true);
		entity->emitterHandle = 0;
	}
}

/*
 ==================
 CG_SetInitialSnapshot

 This will only happen on the very first snapshot, or on map restarts
 ==================
*/
static void CG_SetInitialSnapshot (snapshot_t *snapshot){

	entity_t		*entity;
	entityState_t	*state;
	int				i;

	cg.snapshot = snapshot;

	// Check for entity events this frame
	cg.checkEvents = true;

	// Set up prediction entities this frame
	cg.setupPrediction = true;

	// No error decay on player movement
	cg.thisFrameTeleport = true;

	// Set up the entities
	for (i = 0; i < cg.snapshot->numEntities; i++){
		state = &cg.snapshot->entityStates[i];
		entity = &cgs.entities[state->number];

		entity->state = state;
		entity->nextState = NULL;

		entity->interpolate = false;

		entity->eventSequence = state->eventSequence - MAX_EVENTS;
	}

	// Build the active entity list
	CG_BuildActiveEntityList();
}

/*
 ==================
 CG_SetNextSnapshot

 A new snapshot has just been read in
 ==================
*/
static void CG_SetNextSnapshot (snapshot_t *snapshot){

	entity_t		*entity;
	entityState_t	*state;
	int				i;

	cg.nextSnapshot = snapshot;

	// If changing follow mode, don't interpolate
	if (cg.snapshot->playerState.clientNum != cg.nextSnapshot->playerState.clientNum)
		cg.nextFrameTeleport = true;
	else
		cg.nextFrameTeleport = false;

	// If the next frame is a teleport, we can't interpolate
	if ((cg.snapshot->playerState.flags ^ cg.nextSnapshot->playerState.flags) & EF_TELEPORT)
		cg.nextFrameTeleport = true;

	// Set up the entities
	for (i = 0; i < cg.nextSnapshot->numEntities; i++){
		state = &cg.nextSnapshot->entityStates[i];
		entity = &cgs.entities[state->number];

		entity->nextState = state;

		if (entity->state){
			if (!((entity->state->flags ^ entity->nextState->flags) & EF_TELEPORT))
				entity->interpolate = true;
			else
				entity->interpolate = false;

			continue;
		}

		entity->interpolate = false;

		entity->eventSequence = state->eventSequence - MAX_EVENTS;
	}
}

/*
 ==================
 CG_TransitionSnapshot

 The transition point from cg.snapshot to cg.nextSnapshot has passed
 ==================
*/
static void CG_TransitionSnapshot (){

	snapshot_t		*oldSnapshot;
	entity_t		*entity;
	entityState_t	*state;
	int				i;

	oldSnapshot = cg.snapshot;

	// Reset all entities that aren't present in cg.nextSnapshot
	for (i = 0; i < cg.snapshot->numEntities; i++){
		state = &cg.snapshot->entityStates[i];
		entity = &cgs.entities[state->number];

		if (entity->nextState)
			continue;

		CG_ResetEntity(entity);
	}

	// Move cg.nextSnapshot to cg.snapshot and do the transitions
	cg.snapshot = cg.nextSnapshot;
	cg.nextSnapshot = NULL;

	// Check for entity events this frame
	cg.checkEvents = true;

	// Set up prediction entities this frame
	cg.setupPrediction = true;

	// Teleporting checks are irrespective of prediction
	if ((cg.snapshot->playerState.flags ^ oldSnapshot->playerState.flags) & EF_TELEPORT)
		cg.thisFrameTeleport = true;

	// Transition the entities
	for (i = 0; i < cg.snapshot->numEntities; i++){
		state = &cg.snapshot->entityStates[i];
		entity = &cgs.entities[state->number];

		entity->state = entity->nextState;
		entity->nextState = NULL;

		entity->interpolate = false;
	}

	// Build the active entity list
	CG_BuildActiveEntityList();

	// Transition player state if we are not doing movement prediction for any
	// reason
	if (cg_skipPrediction->integerValue || cgs.demoPlaying || cgs.clientNum != cg.snapshot->playerState.clientNum)
		CG_TransitionPlayerState(&oldSnapshot->playerState, &cg.snapshot->playerState);
}

/*
 ==================
 CG_GetSnapshot

 This may increment cgs.processedSnapshot multiple times if we fail to get a
 valid snapshot
 ==================
*/
static snapshot_t *CG_GetSnapshot (){

	snapshot_t	*snapshot;

	while (cgs.processedSnapshot < cgs.latestSnapshot){
		cgs.processedSnapshot++;

		// Try to get a snapshot
		if (cgs.latestSnapshot - cgs.processedSnapshot >= PACKET_BACKUP)
			continue;

		snapshot = &cgs.snapshots[cgs.processedSnapshot & PACKET_MASK];

		// If valid, return this snapshot
		if (snapshot->valid)
			return snapshot;
	}

	// Nothing left
	return NULL;
}

/*
 ==================
 CG_ProcessSnapshots

 We are trying to set up a renderable view, so determine what the simulated
 time is, and try to get snapshots both before and after that time if
 available.

 If we don't have a valid cg.snapshot after exiting this function, then a 3D
 game view cannot be rendered. This should only happen right after the initial
 connection. After cg.snapshot has been valid once, it will never turn invalid.

 Even if cg.snapshot is valid, cg.nextSnapshot may not be, if the snapshot
 hasn't arrived yet (it becomes an extrapolating situation instead of an
 interpolating one).
 ==================
*/
void CG_ProcessSnapshots (){

	snapshot_t	*snapshot;

	// If we have yet to receive a snapshot, check for it.
	// Once we have gotten the first snapshot, cg.snapshot will always have
	// valid data for the rest of the game.
	if (!cg.snapshot){
		snapshot = CG_GetSnapshot();

		// If we still don't have a snapshot, we can't continue
		if (!snapshot)
			return;

		CG_SetInitialSnapshot(snapshot);
	}

	// Loop until we either have a valid cg.nextSnapshot with a time greater
	// than cg.time to interpolate towards, or we run out of available
	// snapshots
	while (1){
		// If we don't have a next snapshot, try and get a new one
		if (!cg.nextSnapshot){
			snapshot = CG_GetSnapshot();

			// If we still don't have a next snapshot, we will just have to
			// extrapolate
			if (!snapshot)
				break;

			CG_SetNextSnapshot(snapshot);

			if (cg.nextSnapshot->time < cg.snapshot->time)
				G_Error("CG_ProcessSnapshots: server time went backwards");
		}

		// If cg.time < cg.nextSnapshot's, we have a nice interpolating state
		if (cg.time >= cg.snapshot->time && cg.time < cg.nextSnapshot->time)
			break;

		// We have passed the transition from cg.snapshot to cg.nextSnapshot
		CG_TransitionSnapshot();
	}

	// This can happen right after a restart
	if (cg.time < cg.snapshot->time)
		cg.time = cg.snapshot->time;

	// Calculate interpolation fraction
	if (!cg.nextSnapshot || cg.nextSnapshot->time == cg.snapshot->time)
		cg.frameInterpolation = 0.0f;
	else
		cg.frameInterpolation = (float)(cg.time - cg.snapshot->time) / (cg.nextSnapshot->time - cg.snapshot->time);
}